package com.wayhome.srpingbootmybatis.utils.cob;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import sun.security.pkcs.ContentInfo;
import sun.security.pkcs.PKCS7;
import sun.security.pkcs.PKCS9Attributes;
import sun.security.pkcs.SignerInfo;

import javax.security.auth.x500.X500Principal;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Enumeration;

public class PKCS7Tool {

	/** 签名 */
	private static final int SIGNER = 1;
	/** 验证 */
	private static final int VERIFIER = 2;
	/** 用途 */
	private int mode = 0;
	/** 摘要算法 */
	private static String digestAlgorithm = "SHA1";
	/** 签名算法 */
	private static String signingAlgorithm = "SHA1withRSA";
	/** 签名证书 */
	private X509Certificate[] certificates = null;
	/** 签名私钥 */
	private PrivateKey privateKey = null;
	/** 根证书 */
	private Certificate[] rootCertificates = null;
	/** JVM 提供商 */
	private static char jvm = 0;
	private static Class algorithmId = null;
	private static Class derValue = null;
	private static Class objectIdentifier = null;
	private static Class x500Name = null;
	private static boolean debug = false;

	/**
	 * 私有构造方法
	 */
	private PKCS7Tool(int mode) {
		this.mode = mode;
	}

	/**
	 * 匹配私钥用法
	 *
	 * @param keyUsage
	 * @param usage
	 * @return
	 */
	private static boolean matchUsage(boolean[] keyUsage, int usage) {
		if (usage == 0 || keyUsage == null)
			return true;
		for (int i = 0; i < Math.min(keyUsage.length, 32); i++) {
			if ((usage & (1 << i)) != 0 && !keyUsage[i])
				return false;
		}
		return true;
	}

	private static void init() {
		if (jvm != 0)
			return;
		String vendor = System.getProperty("java.vm.vendor");
		if (vendor == null)
			vendor = "";
		String vendorUC = vendor.toUpperCase();
		try {
			if (vendorUC.indexOf("IBM") >= 0) {
				jvm = 'I';
				algorithmId = Class
						.forName("com.ibm.security.x509.AlgorithmId");
				derValue = Class.forName("com.ibm.security.util.DerValue");
				objectIdentifier = Class
						.forName("com.ibm.security.util.ObjectIdentifier");
				x500Name = Class.forName("com.ibm.security.x509.X500Name");
			} else {
				jvm = 'S';
				algorithmId = Class.forName("sun.security.x509.AlgorithmId");
				derValue = Class.forName("sun.security.util.DerValue");
				objectIdentifier = Class
						.forName("sun.security.util.ObjectIdentifier");
				x500Name = Class.forName("sun.security.x509.X500Name");
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
			System.out.println("Not support JRE: " + vendor);
			throw new RuntimeException(e);
		}
	}

	/**
	 * 取得签名工具 加载证书库, 取得签名证书链和私钥
	 *
	 * @param keyStorePath
	 *            证书库路径
	 * @param keyStorePassword
	 *            证书库口令
	 * @param keyPassword
	 *            证书口令
	 * @throws GeneralSecurityException
	 * @throws IOException
	 */
	public static PKCS7Tool getSigner(String keyStorePath,
			String keyStorePassword, String keyPassword)
			throws GeneralSecurityException, IOException {
		String type;
		if (keyStorePath.toLowerCase().endsWith(".pfx"))
			type = "PKCS12";
		else if (keyStorePath.toLowerCase().endsWith(".jks"))
			type = "JKS";
		else
			throw new IllegalStateException("Unknown keystore type.");
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(keyStorePath);
			return getSigner(fis, type, keyStorePassword, keyPassword);
		} finally {
			if (fis != null)
				fis.close();
		}
	}

	/**
	 * 取得签名工具 加载证书库, 取得签名证书链和私钥
	 *
	 * @param keyStorePath
	 *            证书库路径
	 * @param keyStorePassword
	 *            证书库口令
	 * @param keyPassword
	 *            证书口令
	 * @throws GeneralSecurityException
	 * @throws IOException
	 */
	public static PKCS7Tool getSigner(InputStream keyStoreStream, String type,
			String keyStorePassword, String keyPassword)
			throws GeneralSecurityException, IOException {
		init();
		// 加载证书库
		KeyStore keyStore = KeyStore.getInstance(type);
		keyStore.load(keyStoreStream, keyStorePassword.toCharArray());
		// 在证书库中找到签名私钥
		Enumeration aliases = keyStore.aliases();
		String keyAlias = null;
		if (aliases != null) {
			while (aliases.hasMoreElements()) {
				keyAlias = (String) aliases.nextElement();
				Certificate[] certs = keyStore.getCertificateChain(keyAlias);
				if (certs == null || certs.length == 0)
					continue;
				X509Certificate cert = (X509Certificate) certs[0];
				if (matchUsage(cert.getKeyUsage(), 1)) {
					try {
						cert.checkValidity();
					} catch (CertificateException e) {
						continue;
					}
					break;
				}
			}
		}
		// 没有找到可用签名私钥
		if (keyAlias == null)
			throw new GeneralSecurityException(
					"None certificate for sign in this keystore");

		if (debug) {
			System.out.println(keyAlias);
			System.out.println("SIGNER =\n"
					+ new BASE64Encoder().encode(keyStore.getCertificate(
							keyAlias).getEncoded()));
		}
		// 签名证书只能放一张
		X509Certificate certificate = null;
		if (keyStore.isKeyEntry(keyAlias)
				|| keyStore.isCertificateEntry(keyAlias)) {
			certificate = (X509Certificate) keyStore.getCertificate(keyAlias);
		} else {
			throw new GeneralSecurityException(keyAlias
					+ " is unknown to this keystore");
		}

		PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias,
				keyPassword.toCharArray());
		// 没有私钥抛异常
		if (privateKey == null) {
			throw new GeneralSecurityException(keyAlias
					+ " could not be accessed");
		}

		PKCS7Tool tool = new PKCS7Tool(SIGNER);
		tool.certificates = new X509Certificate[] { certificate };
		tool.privateKey = privateKey;
		return tool;
	}

	/**
	 * 取得验签名工具 加载信任根证书
	 *
	 * @param rootCertificatePath
	 *            根证书路径
	 * @throws GeneralSecurityException
	 * @throws IOException
	 */
	public static PKCS7Tool getVerifier(String rootCertificatePath)
			throws GeneralSecurityException, IOException {
		String type;
		if (rootCertificatePath.toLowerCase().endsWith(".p7b"))
			type = "PKCS7";
		else if (rootCertificatePath.toLowerCase().endsWith(".cer"))
			type = "DER";
		else
			throw new IllegalStateException("Unknown rootstore type.");
		FileInputStream fis = null;
		try {
			fis = new FileInputStream(rootCertificatePath);
			return getVerifier(fis, type);
		} finally {
			if (fis != null)
				fis.close();
		}
	}

	/**
	 * 取得验签名工具 加载信任根证书
	 *
	 * @param rootCertStresm
	 *            根证书输入流
	 * @throws GeneralSecurityException
	 * @throws IOException
	 */
	public static PKCS7Tool getVerifier(InputStream rootCertStream, String type)
			throws GeneralSecurityException, IOException {
		init();
		// 加载根证书
		Certificate[] rootCertificates = null;

		CertificateFactory certificatefactory = CertificateFactory
				.getInstance("X.509");
		if ("DER".equals(type)) {
			try {
				rootCertificates = new Certificate[] { certificatefactory
						.generateCertificate(rootCertStream) };
			} catch (Exception exception) {
				if (debug)
					exception.printStackTrace();
				InputStream is = new ByteArrayInputStream(
						new BASE64Decoder().decodeBuffer(rootCertStream));
				rootCertificates = new Certificate[] { certificatefactory
						.generateCertificate(is) };
			}
		} else if ("PKCS7".equals(type)) {
			PKCS7 p7;
			byte[] roots = readData(rootCertStream);
			try {
				p7 = new PKCS7(roots);
			} catch (Exception exception) {
				roots = new BASE64Decoder().decodeBuffer(new String(roots));
				p7 = new PKCS7(roots);
			}
			rootCertificates = p7.getCertificates();
		} else
			throw new IllegalStateException("Unknown root certificate(s) type.");

		PKCS7Tool tool = new PKCS7Tool(VERIFIER);
		tool.rootCertificates = rootCertificates;
		return tool;
	}

	/**
	 * 签名
	 *
	 * @param data
	 *            数据
	 * @return signature 签名结果
	 * @throws GeneralSecurityException
	 * @throws IOException
	 * @throws IllegalArgumentException
	 */
	public String sign(byte[] data) throws Exception {
		if (mode != SIGNER)
			throw new IllegalStateException(
					"call a PKCS7Tool instance not for signature.");

		Signature signer = Signature.getInstance(signingAlgorithm);
		signer.initSign(privateKey);
		signer.update(data, 0, data.length);
		byte[] signedAttributes = signer.sign();

		ContentInfo contentInfo = null;
		Field data_oidField = ContentInfo.class.getField("DATA_OID");
		Object data_oid = data_oidField.get(null);
		Constructor<ContentInfo> contentInfoConstructor = ContentInfo.class
				.getConstructor(new Class[] { data_oid.getClass(), derValue });
		contentInfo = contentInfoConstructor
				.newInstance(new Object[] { data_oid, null });
		// 根证书
		X509Certificate x509 = certificates[certificates.length - 1];
		BigInteger serial = x509.getSerialNumber();
		// X500Name
		Constructor x500NameConstructor = x500Name
				.getConstructor(new Class[] { String.class });
		Object x500NameObject = x500NameConstructor
				.newInstance(new Object[] { x509.getIssuerDN().getName() });
		// AlgorithmId
		Method algorithmIdGet = algorithmId.getMethod("get",
				new Class[] { String.class });
		Object digestAlgorithmId = algorithmIdGet.invoke(null,
				new Object[] { digestAlgorithm });
		Field algorithmIdfield = algorithmId.getField("RSAEncryption_oid");
		Object rsaOid = algorithmIdfield.get(null);
		Constructor algorithmConstructor = algorithmId
				.getConstructor(new Class[] { objectIdentifier });
		Object algorithmRsaOid = algorithmConstructor
				.newInstance(new Object[] { rsaOid });
		// SignerInfo
		Constructor<SignerInfo> signerInfoConstructor = SignerInfo.class
				.getConstructor(new Class[] { x500Name, BigInteger.class,
						algorithmId, PKCS9Attributes.class, algorithmId,
						byte[].class, PKCS9Attributes.class });
		// 签名信息
		SignerInfo si = signerInfoConstructor
				.newInstance(new Object[] { x500NameObject,// X500Name,
															// issuerName,
						serial, // x509.getSerialNumber(), BigInteger serial,
						digestAlgorithmId, // AlgorithmId, digestAlgorithmId,
						null, // PKCS9Attributes, authenticatedAttributes,
						algorithmRsaOid, // AlgorithmId,
											// digestEncryptionAlgorithmId,
						signedAttributes, // byte[] encryptedDigest,
						null // PKCS9Attributes unauthenticatedAttributes)
				});

		SignerInfo[] signerInfos = { si };

		// 构造PKCS7数据
		Object digestAlgorithmIds = Array.newInstance(algorithmId, 1);
		Array.set(digestAlgorithmIds, 0, digestAlgorithmId);
		// PKCS7
		Constructor<PKCS7> pkcs7Constructor = PKCS7.class.getConstructor(new Class[] {
				digestAlgorithmIds.getClass(), ContentInfo.class,
				X509Certificate[].class, signerInfos.getClass() });
		PKCS7 p7 = pkcs7Constructor.newInstance(new Object[] {
				digestAlgorithmIds, contentInfo, certificates, signerInfos });

		ByteArrayOutputStream baout = new ByteArrayOutputStream();
		p7.encodeSignedData(baout);
		// Base64编码
		return (new BASE64Encoder()).encode(baout.toByteArray());
	}

	/**
	 * 验证签名(无CRL)
	 *
	 * @param signature
	 *            签名签名结果
	 * @param data
	 *            被签名数据
	 * @param dn
	 *            签名证书dn, 如果为空则不做匹配验证
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 * @throws SignatureException
	 * @throws InvalidKeyException
	 * @throws CertificateException
	 * @throws NoSuchProviderException
	 */
	public void verify(String signature, byte[] data, String dn)
			throws IOException, NoSuchAlgorithmException, SignatureException,
			InvalidKeyException, CertificateException, NoSuchProviderException {
		if (mode != VERIFIER)
			throw new IllegalStateException(
					"call a PKCS7Tool instance not for verify.");
		byte[] sign = new BASE64Decoder().decodeBuffer(signature);
		PKCS7 p7 = new PKCS7(sign);
		X509Certificate[] certs = p7.getCertificates();
		if (debug)
			for (int i = 0; i < certs.length; i++) {
				X509Certificate cert = certs[i];
				System.out.println("SIGNER " + i + "=\n" + cert);
				System.out.println("SIGNER " + i + "=\n"
						+ new BASE64Encoder().encode(cert.getEncoded()));
			}

		// 验证签名本身、证书用法、证书扩展
		SignerInfo[] sis = p7.verify(data);

		// check the results of the verification
		if (sis == null)
			throw new SignatureException(
					"Signature failed verification, data has been tampered");

		for (int i = 0; i < sis.length; i++) {
			SignerInfo si = sis[i];

			X509Certificate cert = si.getCertificate(p7);
			// 证书是否过期验证，如果不用系统日期可用cert.checkValidity(date);
			cert.checkValidity();
			boolean valid = false;
			for (int j = rootCertificates.length - 1; j >= 0; j--) {
				if (cert.equals(rootCertificates[j])) {
					valid = true;
					break;
				} else {
					// 验证证书签名
					try {
						cert.verify(rootCertificates[j].getPublicKey());
						valid = true;
						break;
					} catch (Exception e) {
					}
				}
			}
			if (!valid)
				throw new SignatureException(
						"Signature certificate's issuer is untrusty.");
			// 验证dn
			if (i == 0 && dn != null) {
				X500Principal name = cert.getSubjectX500Principal();
				if (!dn.equals(name.getName(X500Principal.RFC1779))
						&& !new X500Principal(dn).equals(name))
					throw new SignatureException("Signer dn '"
							+ name.getName(X500Principal.RFC1779)
							+ "' does not matchs '" + dn + "'");
			}
		}
	}

	/**
	 * 读取输入流
	 *
	 * @param in
	 *            输入流
	 * @return 数据
	 * @throws IOException
	 */
	protected static byte[] readData(InputStream in) throws IOException {
		ByteArrayOutputStream bai = new ByteArrayOutputStream();
		byte[] buf = new byte[256];
		int len;
		while ((len = in.read(buf)) > 0)
			bai.write(buf, 0, len);
		return bai.toByteArray();
	}
}
